package sys.xui

import javax.swing.JEditorPane
import javax.swing.text.html.HTMLEditorKit
import javax.swing.JScrollPane
import javax.swing.JPanel
import java.net.URL
import java.net.URI
import javax.swing.JTextField
import javax.swing.JLabel
import javax.swing.JButton
import javax.swing.event.HyperlinkListener
import java.awt.event.ActionListener
import java.awt.BorderLayout
import java.awt.FlowLayout
import java.awt.event.ActionEvent
import javax.swing.event.HyperlinkEvent
import javax.swing.JOptionPane
import java.awt.Font
import javax.swing.text.html.HTMLDocument
import javax.swing.UIManager
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.ScheduledExecutorService
import java.security.MessageDigest
import org.apache.batik.util.gui.xmleditor.XMLEditorKit
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.DocumentBuilder
import org.w3c.dom.Document
import org.w3c.dom.NodeList
import org.w3c.dom.ProcessingInstruction
import javax.xml.xpath.XPathFactory
import javax.xml.xpath.XPath
import javax.xml.xpath.XPathConstants
import javax.xml.transform.TransformerFactory
import javax.xml.transform.stream.StreamResult
import javax.xml.transform.stream.StreamSource
import java.io.StringWriter
import java.io.StringReader

public class XomBrowser extends JPanel implements HyperlinkListener, ActionListener {
  private static final String MIME_TYPE_XML  = "text/xml"
  private static final String MIME_TYPE_HTML = "text/html"
  private JTextField urlField
  private JEditorPane editorPane = null
  private URL url
  private String hashcode = null
  private ScheduledExecutorService scheduledExecutorService = null
  private JButton goButton = null
  private JButton editButton = null
  private JButton cancelButton = null
  private boolean editMode = false
  private HTMLEditorKit htmlEditorKit = null
  private XMLEditorKit xmlEditorKit = null
  private documentBuilderFactory = null
  private documentBuilder = null
  private xpathFactory = null
  private transformerFactory = null
  private String contentType = null

  public XomBrowser() {
    super(new BorderLayout())
    documentBuilderFactory = DocumentBuilderFactory.newInstance()
    documentBuilderFactory.setNamespaceAware(true)
    documentBuilder = documentBuilderFactory.newDocumentBuilder()
    xpathFactory = XPathFactory.newInstance()
    transformerFactory = TransformerFactory.newInstance()
    JPanel topPanel = new JPanel()
    JLabel urlLabel = new JLabel("URL:")
    urlField = new JTextField(30)
    urlField.setText(url.toString())
    urlField.addActionListener(this)
    goButton = new JButton("go")
    goButton.addActionListener(this)
    editButton = new JButton("edit")
    editButton.addActionListener(this)
    cancelButton = new JButton("cancel")
    cancelButton.addActionListener(this)
    cancelButton.setEnabled(false)
    topPanel.add(urlLabel)
    topPanel.add(urlField)
    topPanel.add(goButton)
    topPanel.add(editButton)
    topPanel.add(cancelButton)
    add(topPanel, BorderLayout.NORTH)
    editorPane = new JEditorPane()
    editorPane.setEditable(false)
    editorPane.addHyperlinkListener(this)
    htmlEditorKit = new HTMLEditorKit()
    editorPane.setEditorKitForContentType(MIME_TYPE_HTML, htmlEditorKit)
    xmlEditorKit = new XMLEditorKit()
    editorPane.setEditorKitForContentType(MIME_TYPE_XML, xmlEditorKit)
    editorPane.setEditorKit(htmlEditorKit)
    JScrollPane scrollPane = new JScrollPane(editorPane)
    add(scrollPane, BorderLayout.CENTER)
    setVisible(true)
    def updates = {println "checking ${this.getClass().getName()}"; checkForUpdates()}
    scheduledExecutorService = Executors.newScheduledThreadPool(1)
    scheduledExecutorService.scheduleAtFixedRate(updates, 1000, 500, TimeUnit.MILLISECONDS) 
  }

  public String encodeAsMD5(String message) {
    def digest = MessageDigest.getInstance("MD5")
    digest.update(message.bytes)
    def big = new BigInteger(1, digest.digest())
    def md5 = big.toString(16).padLeft(32,"0")
    return md5
  }

  public String xslt(String xmlDocument, String xslStylesheet) {
    try {
      def xslStringReader = new StringReader(xslStylesheet)
      def xslStreamSource = new StreamSource(xslStringReader)
      def transformer = transformerFactory.newTransformer(xslStreamSource)
      def xmlStringReader = new StringReader(xmlDocument)
      def xmlStreamSource = new StreamSource(xmlStringReader)
      def resultStringWriter = new StringWriter()
      def streamResult = new StreamResult(resultStringWriter)
      transformer.transform(xmlStreamSource, streamResult)
      return resultStringWriter.toString()
    } catch(e) {
      throw new Exception("""Could not apply XSLT.""", e)
    }
  }

  public void applyStylesheet(String stylesheet) {
    def pageText = url.text
  }

  public String getStylesheet(URI uri) {
    try {
      def document = documentBuilder.parse(uri.toString())
      def xpath = xpathFactory.newXPath()
      def expression = xpath.compile("/processing-instruction('xml-stylesheet')")
      def result = expression.evaluate(document, XPathConstants.NODESET)
      def nodes = (NodeList)result
      def stylesheet = null
      for(int nodeIndex=0; nodeIndex<nodes.getLength(); nodeIndex++) {
        def pidata = nodes.item(nodeIndex).getNodeValue()
        def matcher = (pidata =~ /.*href[ ]*[=]+[ ]*["']([A-z0-9\.\/]*)["'].*/)
        //if(matcher.matches()) println("href := "+matcher[0][1])
        if(matcher.matches()) stylesheet = matcher[0][1]
      }
      return stylesheet
    } catch(e) {
      println "warning: ${e.getMessage()}"
      return null
    }
  }

  public void toggleEditMode() {
    editMode = !editMode
    if(editMode) {
      setEditMode()
    } else {
      setViewMode()
    }
  }

  public void setEditMode() {
    editButton.setText("save")
    cancelButton.setEnabled(true)
    editorPane.setEditorKitForContentType(MIME_TYPE_XML, xmlEditorKit)
    editorPane.setContentType(MIME_TYPE_XML)
    editorPane.setText(new File(url.toURI()).text)
    editorPane.setEditable(true)
  }

  public void setViewMode() {
    editButton.setText("edit")
    cancelButton.setEnabled(false)
    editorPane.setContentType(MIME_TYPE_HTML)
    editorPane.setEditorKitForContentType(MIME_TYPE_HTML, htmlEditorKit)
    editorPane.setEditable(true)
    openUrl(url)
  }

  public void checkForUpdates() {
    println "checking for updates..."
    def newHashCode = encodeAsMD5(url.text)
    if(hashCode && hashCode!=newHashCode) { openUrl(url) }
    println "old hashcode:  ${hashCode}"
    println "new hashcode:  ${newHashCode}"
    hashCode = newHashCode
  }

  public void openUrl(String url) {
    if(url.startsWith("/")) {
      openUrl(new URL("file://${url}"))
    } else {
      openUrl(new URL(url))
    }
  }

  public void openUrl(URL url) {
    try {
      if(url.getPath().endsWith("xml")) {
        openXml(url)
      } else if(url.getPath().endsWith("xhtml")) {
        openXml(url)
      } else if(url.getProtocol().startsWith("file")) {
        def file = new File(url.toURI())
        if(file.isFile()) {
          openHtml(url)
        } else {
          openDirectory(file)
        }
      } else {
        openHtml(url)
      }
      this.url = url
      urlField.setText(url.toString())
    } catch(e) {
      reportWarning("Unable to navigate to ${url}:  ${e.getMessage()}")
    }
  }

  private void openDirectory(File directory) {
    contentType = MIME_TYPE_HTML
    def sb = new StringBuilder()
    sb << """<html><title>Directory Listing for ${directory}</title><body>"""
    directory.eachFile() { file ->
      if(file.isDirectory()) {
        sb << """<font color='blue'><b><a href='${file.toURI()}'>${file.getName()}</b></font><br/>"""
      }
      if(file.isFile()) {
        sb << """<font color='green'><i><a href="${file.toURI()}">${file.getName()}</i></font><br/>"""
      }
    }
    sb << """</body></html>"""
    editorPane.getEditorKit().createDefaultDocument()
    editorPane.setText(sb.toString())
    editorPane.setContentType(contentType)
    setStylesheet()
  }

  private void save() {
    try {
      new File(url.toURI()) < editorPane.getText()
    } catch(e) {
      println """Unable to save document "${url}".  ${e.getMessage()}"""
    }
  }

  public void openHtml(URL url) {
    contentType = MIME_TYPE_HTML
    editorPane.setContentType(contentType)
    editorPane.setPage(url)
  }

  private void openXml(URL url) {
    contentType = MIME_TYPE_XML
    def pageText = url.text
    def stylesheet = getStylesheet(url.toURI())
    if(stylesheet) {
      editorPane.setContentType(MIME_TYPE_HTML)
      def stylesheetText = new File(stylesheet).text
      def htmlText = xslt(pageText, stylesheetText)
      setHtml(htmlText)
    } else {
      editorPane.setContentType(MIME_TYPE_XML)
      editorPane.setPage(url)
    }
  }

  public void actionPerformed(ActionEvent event) {
    if (event.getSource() == urlField) {
      openUrl(urlField.getText())
    } else if (event.getSource() == goButton) {
      openUrl(urlField.getText())
    } else if (event.getSource() == editButton) {
      if(editMode) save()
      toggleEditMode()
    } else if (event.getSource() == cancelButton) {
      toggleEditMode()
    }
  }

  public void hyperlinkUpdate(HyperlinkEvent event) {
    if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
      openUrl(event.getURL())
      urlField.setText(event.getURL().toExternalForm())
    }
  }

  private void reportWarning(String message) {
    JOptionPane.showMessageDialog(this, message, "Error", JOptionPane.ERROR_MESSAGE)
    println "warn:  ${message}"
  }

  private void setStylesheet() {
    def font = UIManager.getFont("Label.font")
    def bodyRule = """
            body { 
                   font-family: ${font.getFamily()}; 
                   font-size:   ${font.getSize()} pt; 
                 }
                 """
    ((HTMLDocument)editorPane.getDocument()).getStyleSheet().addRule(bodyRule)
  }

  public void setText(String text) {
    try {
      editorPane.getEditorKit().createDefaultDocument()
      editorPane.setText(text)
    } catch(e) {
      reportWarning("Error setting text.  ${e.getMessage()}")
    }
  }

  public void setHtml(String htmlText) {
    try {
      //editorPane.getEditorKit().createDefaultDocument()
      //editorPane.setContentType(MIME_TYPE_HTML)
      //editorPane.setEditorKitForContentType(MIME_TYPE_HTML, htmlEditorKit)
      //editorPane.setText(htmlText)

      editorPane.getEditorKit().createDefaultDocument()
      editorPane.setText(htmlText)
      editorPane.setContentType(MIME_TYPE_HTML)
      setStylesheet()
      println htmlText
    } catch(e) {
      reportWarning("Error setting hypertext.  ${e.getMessage()}")
    }
  }

}



